@rtype: callable object
"""
return lambda *args, **kwargs: \
- _check_ref(XendNode.instance().get_sr().is_valid_vdi,
+ _check_ref(XendNode.instance().is_valid_vdi,
'VDI_HANDLE_INVALID', func, *args, **kwargs)
def valid_vtpm(func):
@rtype: callable object
"""
return lambda *args, **kwargs: \
- _check_ref(lambda r: XendNode.instance().get_sr().uuid == r,
+ _check_ref(lambda r: XendNode.instance().is_valid_sr,
'SR_HANDLE_INVALID', func, *args, **kwargs)
def valid_pif(func):
# Xen API: Class VBD
# ----------------------------------------------------------------
- VBD_attr_ro = ['image',
- 'io_read_kbs',
+ VBD_attr_ro = ['io_read_kbs',
'io_write_kbs']
VBD_attr_rw = ['VM',
'VDI',
'type',
'driver']
- VBD_attr_inst = VBD_attr_rw + ['image']
+ VBD_attr_inst = VBD_attr_rw
VBD_methods = [('media_change', None)]
VBD_funcs = [('create', 'VBD')]
dom = xendom.get_vm_by_uuid(vbd_struct['VM'])
vbd_ref = ''
try:
- if not vbd_struct.get('VDI', None):
- # this is a traditional VBD without VDI and SR
- vbd_ref = dom.create_vbd(vbd_struct)
- else:
- # new VBD via VDI/SR
- vdi_ref = vbd_struct.get('VDI')
- sr = XendNode.instance().get_sr()
- vdi_image = sr.xen_api_get_by_uuid(vdi_ref)
- if not vdi_image:
- return xen_api_error(['VDI_HANDLE_INVALID', vdi_ref])
- vdi_image = vdi_image.qcow_path
- vbd_ref = dom.create_vbd_with_vdi(vbd_struct, vdi_image)
+ # new VBD via VDI/SR
+ vdi_ref = vbd_struct.get('VDI')
+ vdi = XendNode.instance().get_vdi_by_uuid(vdi_ref)
+ if not vdi:
+ return xen_api_error(['VDI_HANDLE_INVALID', vdi_ref])
+ vdi_image = vdi.get_image_uri()
+ vbd_ref = XendTask.log_progress(0, 100,
+ dom.create_vbd,
+ vbd_struct, vdi_image)
except XendError:
return xen_api_todo()
if not vm:
return xen_api_error(['VBD_HANDLE_INVALID', vbd_ref])
- vm.destroy_vbd(vbd_ref)
+ XendTask.log_progress(0, 100, vm.destroy_vbd, vbd_ref)
return xen_api_success_void()
# attributes (rw)
('get_by_name_label', 'Set(VDI)')]
def _get_VDI(self, ref):
- return XendNode.instance().get_sr().xen_api_get_by_uuid(ref)
+ return XendNode.instance().get_vdi_by_uuid(ref)
def VDI_get_VBDs(self, session, vdi_ref):
return xen_api_todo()
return xen_api_success(self._get_VDI(vdi_ref).name_description)
def VDI_get_SR(self, session, vdi_ref):
- sr = XendNode.instance().get_sr()
- return xen_api_success(sr.uuid)
+ return xen_api_success(self._get_VDI(vdi_ref).sr_uuid)
def VDI_get_virtual_size(self, session, vdi_ref):
return xen_api_success(self._get_VDI(vdi_ref).virtual_size)
return xen_api_todo()
def VDI_destroy(self, session, vdi_ref):
- sr = XendNode.instance().get_sr()
- sr.destroy_image(vdi_ref)
+ sr = XendNode.instance().get_sr_containing_vdi(vdi_ref)
+ sr.destroy_vdi(vdi_ref)
return xen_api_success_void()
def VDI_get_record(self, session, vdi_ref):
- sr = XendNode.instance().get_sr()
- image = sr.xen_api_get_by_uuid(vdi_ref)
+ image = XendNode.instance().get_vdi_by_uuid(vdi_ref)
return xen_api_success({
'uuid': vdi_ref,
'name_label': image.name_label,
'name_description': image.name_description,
- 'SR': sr.uuid,
+ 'SR': image.sr_uuid,
'VBDs': [], # TODO
'virtual_size': image.virtual_size,
'physical_utilisation': image.physical_utilisation,
# Class Functions
def VDI_create(self, session, vdi_struct):
- sr = XendNode.instance().get_sr()
sr_ref = vdi_struct.get('SR')
+ xennode = XendNode.instance()
+ if not xennode.is_valid_sr(sr_ref):
+ return xen_api_error(['SR_HANDLE_INVALID', sr_ref])
- if sr.uuid != sr_ref:
- return xen_api_error(['SR_HANDLE_INVALID', vdi_struct.get('SR')])
-
- vdi_uuid = sr.create_image(vdi_struct)
+ vdi_uuid = xennode.srs[sr_ref].create_vdi(vdi_struct)
return xen_api_success(vdi_uuid)
def VDI_get_all(self, session):
- sr = XendNode.instance().get_sr()
- return xen_api_success(sr.list_images())
+ xennode = XendNode.instance()
+ vdis = [sr.get_vdis() for sr in xennode.srs.values()]
+ return xen_api_success(reduce(lambda x, y: x + y, vdis))
def VDI_get_by_name_label(self, session, name):
- sr = XendNode.instance().get_sr()
- image_uuid = sr.xen_api_get_by_name_label(name)
- if image_uuid:
- return xen_api_success([image_uuid])
- return xen_api_success([])
+ xennode = XendNode.instance()
+ return xen_api_success(xennode.get_vdi_by_name_label(name))
# Xen API: Class VTPM
# Class Functions
def SR_get_all(self, session):
- sr = XendNode.instance().get_sr()
- return xen_api_success([sr.uuid])
-
+ return xen_api_success(XendNode.instance().get_all_sr_uuid())
+
def SR_get_by_name_label(self, session, label):
- sr = XendNode.instance().get_sr()
- if sr.name_label != label:
- return xen_api_success([])
- return xen_api_success([sr.uuid])
-
+ return xen_api_success(XendNode.instance().get_sr_by_name(label))
+
def SR_create(self, session):
return xen_api_error(XEND_ERROR_UNSUPPORTED)
return xen_api_error(XEND_ERROR_UNSUPPORTED)
def SR_get_record(self, session, sr_ref):
- sr = XendNode.instance().get_sr()
- return xen_api_success(sr.get_record())
+ sr = XendNode.instance().get_sr(sr_ref)
+ if sr:
+ return xen_api_success(sr.get_record())
+ return xen_api_error(['SR_HANDLE_INVALID', sr_ref])
# Attribute acceess
- def _get_SR_func(self, _, func):
- return xen_api_success(getattr(XendNode.instance().get_sr(), func)())
+ def _get_SR_func(self, sr_ref, func):
+ return xen_api_success(getattr(XendNode.instance().get_sr(sr_ref),
+ func)())
- def _get_SR_attr(self, _, attr):
- return xen_api_success(getattr(XendNode.instance().get_sr(), attr))
+ def _get_SR_attr(self, sr_ref, attr):
+ return xen_api_success(getattr(XendNode.instance().get_sr(sr_ref),
+ attr))
def SR_get_VDIs(self, _, ref):
return self._get_SR_func(ref, 'list_images')
return self._get_SR_func(ref, 'virtual_allocation')
def SR_get_physical_utilisation(self, _, ref):
- return self._get_SR_func(ref, 'used_space_bytes')
+ return self._get_SR_func(ref, 'physical_utilisation')
def SR_get_physical_size(self, _, ref):
- return self._get_SR_func(ref, 'total_space_bytes')
+ return self._get_SR_func(ref, 'physical_size')
def SR_get_type(self, _, ref):
return self._get_SR_attr(ref, 'type')
return self._get_SR_attr(ref, 'name_description')
def SR_set_name_label(self, session, sr_ref, value):
- sr = XendNode.instance().get_sr()
- sr.name_label = value
- XendNode.instance().save()
+ sr = XendNode.instance.get_sr(sr_ref)
+ if sr:
+ sr.name_label = value
+ XendNode.instance().save()
return xen_api_success_void()
def SR_set_name_description(self, session, sr_ref, value):
- sr = XendNode.instance().get_sr()
- sr.name_description = value
- XendNode.instance().save()
+ sr = XendNode.instance.get_sr(sr_ref)
+ if sr:
+ sr.name_description = value
+ XendNode.instance().save()
return xen_api_success_void()
return dev_uuid
elif dev_type in ('vbd', 'tap'):
- if dev_type == 'vbd':
- dev_info['uname'] = cfg_xenapi.get('image', '')
- dev_info['dev'] = '%s:disk' % cfg_xenapi.get('device')
- elif dev_type == 'tap':
- dev_info['uname'] = 'tap:qcow:%s' % cfg_xenapi.get('image')
- dev_info['dev'] = '%s:disk' % cfg_xenapi.get('device')
+ dev_info['type'] = cfg_xenapi.get('type', 'Disk')
+ if dev_info['type'] == 'CD':
+ old_vbd_type = 'cdrom'
+ else:
+ old_vbd_type = 'disk'
+ dev_info['uname'] = cfg_xenapi.get('image', '')
+ dev_info['dev'] = '%s:%s' % (cfg_xenapi.get('device'),
+ old_vbd_type)
dev_info['driver'] = cfg_xenapi.get('driver')
dev_info['VDI'] = cfg_xenapi.get('VDI', '')
from xen.xend import XendDomain
dom0 = XendDomain.instance().privilegedDomain()
- dom0._waitForDeviceUUID(dom0.create_vbd_with_vdi(vbd, fn))
+ dom0._waitForDeviceUUID(dom0.create_vbd(vbd, fn))
fn = BOOTLOADER_LOOPBACK_DEVICE
try:
def get_vtpms(self):
return self.info.get('vtpm_refs', [])
- def create_vbd(self, xenapi_vbd):
- """Create a VBD device from the passed struct in Xen API format.
-
- @return: uuid of the device
- @rtype: string
- """
-
- dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
- if not dev_uuid:
- raise XendError('Failed to create device')
-
- if self.state == XEN_API_VM_POWER_STATE_RUNNING:
- _, config = self.info['devices'][dev_uuid]
- config['devid'] = self.getDeviceController('vbd').createDevice(config)
-
- return dev_uuid
-
- def create_vbd_with_vdi(self, xenapi_vbd, vdi_image_path):
+ def create_vbd(self, xenapi_vbd, vdi_image_path):
"""Create a VBD using a VDI from XendStorageRepository.
@param xenapi_vbd: vbd struct from the Xen API
@return: uuid of the device
"""
xenapi_vbd['image'] = vdi_image_path
- log.debug('create_vbd_with_vdi: %s' % xenapi_vbd)
- dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
+ log.debug('create_vbd: %s' % xenapi_vbd)
+ dev_uuid = ''
+ if vdi_image_path.startswith('tap'):
+ dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
+ else:
+ dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
+
if not dev_uuid:
raise XendError('Failed to create device')
if self.state == XEN_API_VM_POWER_STATE_RUNNING:
_, config = self.info['devices'][dev_uuid]
- config['devid'] = self.getDeviceController('tap').createDevice(config)
+ dev_control = None
+
+ if vdi_image_path.startswith('tap'):
+ dev_control = self.getDeviceController('tap')
+ else:
+ dev_control = self.getDeviceController('vbd')
+
+ config['devid'] = dev_control.createDevice(config)
return dev_uuid
--- /dev/null
+#!/usr/bin/python
+#============================================================================
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of version 2.1 of the GNU Lesser General Public
+# License as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#============================================================================
+# Copyright (C) 2007 XenSource Ltd.
+#============================================================================
+#
+# A pseudo-StorageRepository to provide a representation for the images
+# that can be specified by xm.
+#
+
+import commands
+import logging
+import os
+import stat
+import threading
+import re
+import sys
+import struct
+
+from xen.util import mkdir
+from xen.xend import uuid
+from xen.xend.XendError import XendError
+from xen.xend.XendVDI import *
+from xen.xend.XendTask import XendTask
+from xen.xend.XendStorageRepository import XendStorageRepository
+from xen.xend.XendStateStore import XendStateStore
+from xen.xend.XendOptions import instance as xendoptions
+
+MB = 1024 * 1024
+
+log = logging.getLogger("xend.XendLocalStorageRepo")
+
+class XendLocalStorageRepo(XendStorageRepository):
+ """A backwards compatibility storage repository so that
+ traditional file:/dir/file.img and phy:/dev/hdxx images can
+ still be represented in terms of the Xen API.
+ """
+
+ def __init__(self, sr_uuid, sr_type = 'local',
+ name_label = 'Local',
+ name_description = 'Traditional Local Storage Repo'):
+ """
+ @ivar images: mapping of all the images.
+ @type images: dictionary by image uuid.
+ @ivar lock: lock to provide thread safety.
+ """
+
+ XendStorageRepository.__init__(self, sr_uuid, sr_type,
+ name_label, name_description,
+ '/')
+
+ self.state = XendStateStore(xendoptions().get_xend_state_path()
+ + '/local_sr')
+
+ stored_images = self.state.load_state('vdi')
+ if stored_images:
+ for image_uuid, image in stored_images.items():
+ self.images[image_uuid] = XendLocalVDI(image)
+
+ def create_vdi(self, vdi_struct):
+ """ Creates a fake VDI image for a traditional image string.
+
+ The image uri is stored in the attribute 'uri'
+ """
+ vdi_struct['uuid'] = uuid.createString()
+ vdi_struct['SR'] = self.uuid
+ new_image = XendLocalVDI(vdi_struct)
+ self.images[new_image.uuid] = new_image
+ self.save_state()
+ return new_image.uuid
+
+ def save_state(self):
+ vdi_records = dict([(k, v.get_record(transient = False))
+ for k, v in self.images.items()])
+ self.state.save_state('vdi', vdi_records)
+
+ def destroy_vdi(self, vdi_uuid):
+ if vdi_uuid in self.images:
+ del self.images[vdi_uuid]
+
+ self.save_state()
+
from xen.xend import uuid
from xen.xend.XendError import XendError, NetworkAlreadyConnected
from xen.xend.XendOptions import instance as xendoptions
-from xen.xend.XendStorageRepository import XendStorageRepository
+from xen.xend.XendQCoWStorageRepo import XendQCoWStorageRepo
+from xen.xend.XendLocalStorageRepo import XendLocalStorageRepo
from xen.xend.XendLogging import log
from xen.xend.XendPIF import *
from xen.xend.XendNetwork import *
self.pifs = {}
self.networks = {}
-
+ self.srs = {}
+
# initialise networks
saved_networks = self.state_store.load_state('network')
if saved_networks:
self.PIF_create(name, mtu, '', mac, network, False)
# initialise storage
- saved_sr = self.state_store.load_state('sr')
- if saved_sr and len(saved_sr) == 1:
- sr_uuid = saved_sr.keys()[0]
- self.sr = XendStorageRepository(sr_uuid)
- else:
- sr_uuid = uuid.createString()
- self.sr = XendStorageRepository(sr_uuid)
+ saved_srs = self.state_store.load_state('sr')
+ if saved_srs:
+ for sr_uuid, sr_cfg in saved_srs.items():
+ if sr_cfg['type'] == 'qcow_file':
+ self.srs[sr_uuid] = XendQCoWStorageRepo(sr_uuid)
+ elif sr_cfg['type'] == 'local_image':
+ self.srs[sr_uuid] = XendLocalStorageRepo(sr_uuid)
+
+ # Create missing SRs if they don't exist
+ if not self.get_sr_by_type('local_image'):
+ image_sr_uuid = uuid.createString()
+ self.srs[image_sr_uuid] = XendLocalStorageRepo(image_sr_uuid)
+
+ if not self.get_sr_by_type('qcow_file'):
+ qcow_sr_uuid = uuid.createString()
+ self.srs[qcow_sr_uuid] = XendQCowStorageRepo(qcow_sr_uuid)
+
def network_create(self, name_label, name_description,
self.state_store.save_state('cpu', self.cpus)
self.save_PIFs()
self.save_networks()
-
- sr_record = {self.sr.uuid: self.sr.get_record()}
- self.state_store.save_state('sr', sr_record)
+ self.save_SRs()
def save_PIFs(self):
pif_records = dict([(k, v.get_record(transient = False))
for k, v in self.networks.items()])
self.state_store.save_state('network', net_records)
+ def save_SRs(self):
+ sr_records = dict([(k, v.get_record(transient = False))
+ for k, v in self.srs.items()])
+ self.state_store.save_state('sr', sr_records)
+
def shutdown(self):
return 0
def notify(self, _):
return 0
-
#
# Ref validation
def is_valid_network(self, network_ref):
return (network_ref in self.networks)
+ def is_valid_sr(self, sr_ref):
+ return (sr_ref in self.srs)
+
+ def is_valid_vdi(self, vdi_ref):
+ for sr in self.srs.values():
+ if sr.is_valid_vdi(vdi_ref):
+ return True
+ return False
+
#
- # Storage Repo
+ # Storage Repositories
#
- def get_sr(self):
- return self.sr
+ def get_sr(self, sr_uuid):
+ return self.srs.get(sr_uuid)
+
+ def get_sr_by_type(self, sr_type):
+ return [sr.uuid for sr in self.srs.values() if sr.type == sr_type]
+
+ def get_sr_by_name(self, name):
+ return [sr.uuid for sr in self.srs.values() if sr.name_label == name]
+
+ def get_all_sr_uuid(self):
+ return self.srs.keys()
+
+ def get_vdi_by_uuid(self, vdi_uuid):
+ for sr in self.srs.values():
+ if sr.is_valid_vdi(vdi_uuid):
+ return sr.get_vdi_by_uuid(vdi_uuid)
+ return None
+
+ def get_vdi_by_name_label(self, name):
+ for sr in self.srs.values():
+ vdi = sr.get_vdi_by_name_label(name)
+ if vdi:
+ return vdi
+ return None
+
+ def get_sr_containing_vdi(self, vdi_uuid):
+ for sr in self.srs.values():
+ if sr.is_valid_vdi(vdi_uuid):
+ return sr
+ return None
+
#
# Host Functions
"""Default xend management state storage."""
xend_state_path_default = '/var/lib/xend/state'
+ """Default xend QCoW storage repository location."""
+ xend_storage_path_default = '/var/lib/xend/storage'
+
"""Default type of backend network interfaces"""
netback_type = osdep.netback_type
def get_xend_state_path(self):
""" Get the path for persistent domain configuration storage
"""
- return self.get_config_string("xend-state-path", self.xend_state_path_default)
+ return self.get_config_string("xend-state-path", self.xend_state_path_default)
+
+ def get_xend_storage_path(self):
+ """ Get the path for persistent domain configuration storage
+ """
+ return self.get_config_string("xend-storage-path", self.xend_storage_path_default)
def get_network_script(self):
"""@return the script used to alter the network configuration when
--- /dev/null
+#!/usr/bin/python
+#============================================================================
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of version 2.1 of the GNU Lesser General Public
+# License as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#============================================================================
+# Copyright (C) 2006,2007 XenSource Ltd.
+#============================================================================
+#
+# The default QCOW Xen API Storage Repository
+#
+
+import commands
+import logging
+import os
+import stat
+import threading
+import re
+import sys
+import struct
+
+from xen.util import mkdir
+from xen.xend import uuid
+from xen.xend.XendError import XendError
+from xen.xend.XendVDI import *
+from xen.xend.XendTask import XendTask
+from xen.xend.XendStorageRepository import XendStorageRepository
+from xen.xend.XendOptions import instance as xendoptions
+
+XEND_STORAGE_NO_MAXIMUM = sys.maxint
+XEND_STORAGE_QCOW_FILENAME = "%s.qcow"
+XEND_STORAGE_VDICFG_FILENAME = "%s.vdi.xml"
+QCOW_CREATE_COMMAND = "/usr/sbin/qcow-create -r %d %s"
+
+MB = 1024 * 1024
+
+log = logging.getLogger("xend.XendQCowStorageRepo")
+
+
+def qcow_virtual_size(qcow_file):
+ """Read the first 32 bytes of the QCoW header to determine its size.
+
+ See: http://www.gnome.org/~markmc/qcow-image-format.html.
+ """
+ try:
+ qcow_header = open(qcow_file, 'rb').read(32)
+ parts = struct.unpack('>IIQIIQ', qcow_header)
+ return parts[-1]
+ except IOError:
+ return -1
+
+class XendQCoWStorageRepo(XendStorageRepository):
+ """A simple file backed QCOW Storage Repository.
+
+ This class exposes the interface to create VDI's via the
+ Xen API. The backend is a file-backed QCOW format that is stored
+ in XEND_STORAGE_DIR or any that is specified in the constructor.
+
+ The actual images are created in the format <uuid>.img and <uuid>.qcow.
+ """
+
+ def __init__(self, sr_uuid,
+ sr_type = "qcow_file",
+ name_label = "QCoW",
+ name_description = "Xend QCoW Storage Repository",
+ location = xendoptions().get_xend_storage_path(),
+ storage_max = XEND_STORAGE_NO_MAXIMUM):
+ """
+ @keyword storage_max: Maximum disk space to use in bytes.
+ @type storage_max: int
+
+ @ivar storage_free: storage space free for this repository
+ @ivar images: mapping of all the images.
+ @type images: dictionary by image uuid.
+ @ivar lock: lock to provide thread safety.
+ """
+
+ XendStorageRepository.__init__(self, sr_uuid, sr_type, name_label,
+ name_description, location,
+ storage_max)
+ self.storage_free = 0
+ self._refresh()
+
+ def get_record(self, transient = True):
+ retval = {'uuid': self.uuid,
+ 'name_label': self.name_label,
+ 'name_description': self.name_description,
+ 'virtual_allocation': self.virtual_allocation,
+ 'physical_utilisation': self.physical_utilisation,
+ 'physical_size': self.physical_size,
+ 'type': self.type,
+ 'location': self.location,
+ 'VDIs': self.images.keys()}
+
+ if self.physical_size == XEND_STORAGE_NO_MAXIMUM:
+ stfs = os.statvfs(self.location)
+ retval['physical_size'] = stfs.f_blocks * stfs.f_frsize
+
+ return retval
+
+ def _refresh(self):
+ """Internal function that refreshes the state of the disk and
+ updates the list of images available.
+ """
+ self.lock.acquire()
+ try:
+ mkdir.parents(self.location, stat.S_IRWXU)
+
+ # scan the directory and populate self.images
+ virtual_alloc = 0
+ physical_used = 0
+ seen_images = []
+ for filename in os.listdir(self.location):
+ if filename[-5:] == XEND_STORAGE_QCOW_FILENAME[-5:]:
+ image_uuid = filename[:-5]
+ seen_images.append(image_uuid)
+
+ qcow_file = XEND_STORAGE_QCOW_FILENAME % image_uuid
+ cfg_file = XEND_STORAGE_VDICFG_FILENAME % image_uuid
+ qcow_path = os.path.join(self.location, qcow_file)
+ cfg_path = os.path.join(self.location, cfg_file)
+
+ phys_size = os.stat(qcow_path).st_size
+ virt_size = qcow_virtual_size(qcow_path)
+
+ # add this image if we haven't seen it before
+ if image_uuid not in self.images:
+ vdi = XendQCoWVDI(image_uuid, self.uuid,
+ qcow_path, cfg_path,
+ virt_size, phys_size)
+
+ if cfg_path and os.path.exists(cfg_path):
+ try:
+ vdi.load_config(cfg_path)
+ except:
+ log.error('Corrupt VDI configuration file %s' %
+ cfg_path)
+
+ self.images[image_uuid] = vdi
+
+ physical_used += phys_size
+ virtual_alloc += virt_size
+
+ # remove images that aren't valid
+ for image_uuid in self.images.keys():
+ if image_uuid not in seen_images:
+ try:
+ os.unlink(self.images[image_uuid].qcow_path)
+ except OSError:
+ pass
+ del self.images[image_uuid]
+
+ self.virtual_allocation = virtual_alloc
+ self.physical_utilisation = physical_used
+
+ # update free storage if we have to track that
+ if self.physical_size == XEND_STORAGE_NO_MAXIMUM:
+ self.storage_free = self._get_free_space()
+ else:
+ self.storage_free = self.physical_size - self.virtual_allocation
+
+ finally:
+ self.lock.release()
+
+ def _get_free_space(self):
+ """Returns the amount of free space in bytes available in the storage
+ partition. Note that this may not be used if the storage repository
+ is initialised with a maximum size in storage_max.
+
+ @rtype: int
+ """
+ stfs = os.statvfs(self.location)
+ return stfs.f_bavail * stfs.f_frsize
+
+ def _has_space_available_for(self, size_bytes):
+ """Returns whether there is enough space for an image in the
+ partition which the storage_dir resides on.
+
+ @rtype: bool
+ """
+ if self.physical_size != XEND_STORAGE_NO_MAXIMUM:
+ return self.storage_free > size_bytes
+
+ bytes_free = self._get_free_space()
+ if size_bytes < bytes_free:
+ return True
+ return False
+
+ def _create_image_files(self, desired_size_bytes):
+ """Create an image and return its assigned UUID.
+
+ @param desired_size_bytes: Desired image size in bytes
+ @type desired_size_bytes: int
+ @rtype: string
+ @return: uuid
+
+ @raises XendError: If an error occurs.
+ """
+ self.lock.acquire()
+ try:
+ if not self._has_space_available_for(desired_size_bytes):
+ raise XendError("Not enough space")
+
+ image_uuid = uuid.createString()
+ qcow_path = os.path.join(self.location,
+ XEND_STORAGE_QCOW_FILENAME % image_uuid)
+
+ if qcow_path and os.path.exists(qcow_path):
+ raise XendError("Image with same UUID alreaady exists:" %
+ image_uuid)
+
+ cmd = QCOW_CREATE_COMMAND % (desired_size_bytes/MB, qcow_path)
+ rc, output = commands.getstatusoutput(cmd)
+
+ if rc != 0:
+ # cleanup the image file
+ os.unlink(qcow_path)
+ raise XendError("Failed to create QCOW Image: %s" % output)
+
+ self._refresh()
+ return image_uuid
+ finally:
+ self.lock.release()
+
+ def destroy_vdi(self, image_uuid):
+ """Destroy an image that is managed by this storage repository.
+
+ @param image_uuid: Image UUID
+ @type image_uuid: String
+ @rtype: String
+ """
+ self.lock.acquire()
+ try:
+ if image_uuid in self.images:
+ # TODO: check if it is being used?
+ qcow_path = self.images[image_uuid].qcow_path
+ cfg_path = self.images[image_uuid].cfg_path
+ try:
+ os.unlink(qcow_path)
+ if cfg_path and os.path.exists(cfg_path):
+ os.unlink(cfg_path)
+ except OSError:
+ log.exception("Failed to destroy image")
+ del self.images[image_uuid]
+ return True
+ finally:
+ self.lock.release()
+
+ return False
+
+ def list_images(self):
+ """ List all the available images by UUID.
+
+ @rtype: list of strings.
+ @return: list of UUIDs
+ """
+ self.lock.acquire()
+ try:
+ return self.images.keys()
+ finally:
+ self.lock.release()
+
+ def free_space_bytes(self):
+ """Returns the amount of available space in KB.
+ @rtype: int
+ """
+ self.lock.acquire()
+ try:
+ return self.storage_free
+ finally:
+ self.lock.release()
+
+ def total_space_bytes(self):
+ """Returns the total usable space of the storage repo in KB.
+ @rtype: int
+ """
+ self.lock.acquire()
+ try:
+ if self.physical_size == XEND_STORAGE_NO_MAXIMUM:
+ stfs = os.statvfs(self.location)
+ return stfs.f_blocks * stfs.f_frsize
+ else:
+ return self.physical_size
+ finally:
+ self.lock.release()
+
+ def used_space_bytes(self):
+ """Returns the total amount of space used by this storage repository.
+ @rtype: int
+ """
+ self.lock.acquire()
+ try:
+ return self.physical_utilisation
+ finally:
+ self.lock.release()
+
+ def virtual_allocation(self):
+ """Returns the total virtual space allocated within the storage repo.
+ @rtype: int
+ """
+ self.lock.acquire()
+ try:
+ return self.virtual_allocation
+ finally:
+ self.lock.release()
+
+
+ def create_vdi(self, vdi_struct):
+ image_uuid = None
+ try:
+ sector_count = int(vdi_struct.get('virtual_size', 0))
+ sector_size = int(vdi_struct.get('sector_size', 1024))
+ size_bytes = (sector_count * sector_size)
+
+ image_uuid = self._create_image_files(size_bytes)
+
+ image = self.images[image_uuid]
+ image_cfg = {
+ 'sector_size': sector_size,
+ 'virtual_size': sector_count,
+ 'type': vdi_struct.get('type', 'system'),
+ 'name_label': vdi_struct.get('name_label', ''),
+ 'name_description': vdi_struct.get('name_description', ''),
+ 'sharable': bool(vdi_struct.get('sharable', False)),
+ 'read_only': bool(vdi_struct.get('read_only', False)),
+ }
+
+ # load in configuration from vdi_struct
+ image.load_config_dict(image_cfg)
+
+ # save configuration to file
+ cfg_filename = XEND_STORAGE_VDICFG_FILENAME % image_uuid
+ cfg_path = os.path.join(self.location, cfg_filename)
+ image.save_config(cfg_path)
+
+ except Exception, e:
+ # cleanup before raising exception
+ if image_uuid:
+ self.destroy_vdi(image_uuid)
+
+ raise
+
+ return image_uuid
+
+
+# remove everything below this line!! for testing only
+if __name__ == "__main__":
+ xsr = XendStorageRepository()
+ print 'Free Space: %d MB' % (xsr.free_space_bytes()/MB)
+ print "Create Image:",
+ print xsr._create_image_files(10 * MB)
+ print 'Delete all images:'
+ for image_uuid in xsr.list_images():
+ print image_uuid,
+ xsr._destroy_image_files(image_uuid)
+
+ print
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#============================================================================
-# Copyright (C) 2006 XenSource Ltd.
+# Copyright (C) 2006, 2007 XenSource Ltd.
#============================================================================
#
-# The default QCOW Xen API Storage Repository
+# Abstract class for XendStorageRepositories
#
-import commands
-import logging
-import os
-import stat
import threading
-import re
import sys
-import struct
-from xen.util import mkdir
-from xen.xend import uuid
from xen.xend.XendError import XendError
from xen.xend.XendVDI import *
-
XEND_STORAGE_NO_MAXIMUM = sys.maxint
-XEND_STORAGE_DIR = "/var/lib/xend/storage/"
-XEND_STORAGE_QCOW_FILENAME = "%s.qcow"
-XEND_STORAGE_VDICFG_FILENAME = "%s.vdi.xml"
-QCOW_CREATE_COMMAND = "/usr/sbin/qcow-create -r %d %s"
-
-MB = 1024 * 1024
-
-log = logging.getLogger("xend.XendStorageRepository")
-
-
-def qcow_virtual_size(qcow_file):
- """Read the first 32 bytes of the QCoW header to determine its size.
-
- See: http://www.gnome.org/~markmc/qcow-image-format.html.
- """
- try:
- qcow_header = open(qcow_file, 'rb').read(32)
- parts = struct.unpack('>IIQIIQ', qcow_header)
- return parts[-1]
- except IOError:
- return -1
class XendStorageRepository:
- """A simple file backed QCOW Storage Repository.
-
- This class exposes the interface to create VDI's via the
- Xen API. The backend is a file-backed QCOW format that is stored
- in XEND_STORAGE_DIR or any that is specified in the constructor.
+ """ Base class for Storage Repos. """
- The actual images are created in the format <uuid>.img and <uuid>.qcow.
- """
-
def __init__(self, uuid,
- sr_type = "qcow_file",
- name_label = "Local",
- name_description = "Xend Storage Repository",
- location = XEND_STORAGE_DIR,
+ sr_type = "unknown",
+ name_label = 'Unknown',
+ name_description = 'Not Implemented',
+ location = '',
storage_max = XEND_STORAGE_NO_MAXIMUM):
"""
@keyword storage_max: Maximum disk space to use in bytes.
self.name_description = name_description
self.images = {}
- self.storage_max = storage_max
- self.storage_free = 0
- self.storage_used = 0
- self.storage_alloc = 0
-
+ self.physical_size = storage_max
+ self.physical_utilisation = 0
+ self.virtual_allocation = 0
+
self.lock = threading.RLock()
- self._refresh()
- def get_record(self):
+ def get_record(self, transient = True):
retval = {'uuid': self.uuid,
'name_label': self.name_label,
'name_description': self.name_description,
- 'virtual_allocation': self.storage_alloc,
- 'physical_utilisation': self.storage_used,
- 'physical_size': self.storage_max,
+ 'virtual_allocation': self.virtual_allocation,
+ 'physical_utilisation': self.physical_utilisation,
+ 'physical_size': self.physical_size,
'type': self.type,
'location': self.location,
'VDIs': self.images.keys()}
-
- if self.storage_max == XEND_STORAGE_NO_MAXIMUM:
- stfs = os.statvfs(self.location)
- retval['physical_size'] = stfs.f_blocks * stfs.f_frsize
return retval
-
- def _refresh(self):
- """Internal function that refreshes the state of the disk and
- updates the list of images available.
- """
- self.lock.acquire()
- try:
- mkdir.parents(self.location, stat.S_IRWXU)
-
- # scan the directory and populate self.images
- virtual_alloc = 0
- physical_used = 0
- seen_images = []
- for filename in os.listdir(self.location):
- if filename[-5:] == XEND_STORAGE_QCOW_FILENAME[-5:]:
- image_uuid = filename[:-5]
- seen_images.append(image_uuid)
-
- qcow_file = XEND_STORAGE_QCOW_FILENAME % image_uuid
- cfg_file = XEND_STORAGE_VDICFG_FILENAME % image_uuid
- qcow_path = os.path.join(self.location, qcow_file)
- cfg_path = os.path.join(self.location, cfg_file)
-
- phys_size = os.stat(qcow_path).st_size
- virt_size = qcow_virtual_size(qcow_path)
-
- # add this image if we haven't seen it before
- if image_uuid not in self.images:
- vdi = XendQCOWVDI(image_uuid, self.uuid,
- qcow_path, cfg_path,
- virt_size, phys_size)
-
- if cfg_path and os.path.exists(cfg_path):
- try:
- vdi.load_config(cfg_path)
- except:
- log.error('Corrupt VDI configuration file %s' %
- cfg_path)
-
- self.images[image_uuid] = vdi
-
- physical_used += phys_size
- virtual_alloc += virt_size
-
- # remove images that aren't valid
- for image_uuid in self.images.keys():
- if image_uuid not in seen_images:
- try:
- os.unlink(self.images[image_uuid].qcow_path)
- except OSError:
- pass
- del self.images[image_uuid]
-
- self.storage_alloc = virtual_alloc
- self.storage_used = physical_used
-
- # update free storage if we have to track that
- if self.storage_max == XEND_STORAGE_NO_MAXIMUM:
- self.storage_free = self._get_free_space()
- else:
- self.storage_free = self.storage_max - self.storage_alloc
-
- finally:
- self.lock.release()
-
- def _get_free_space(self):
- """Returns the amount of free space in bytes available in the storage
- partition. Note that this may not be used if the storage repository
- is initialised with a maximum size in storage_max.
-
- @rtype: int
- """
- stfs = os.statvfs(self.location)
- return stfs.f_bavail * stfs.f_frsize
-
- def _has_space_available_for(self, size_bytes):
- """Returns whether there is enough space for an image in the
- partition which the storage_dir resides on.
-
- @rtype: bool
- """
- if self.storage_max != XEND_STORAGE_NO_MAXIMUM:
- return self.storage_free > size_bytes
-
- bytes_free = self._get_free_space()
- if size_bytes < bytes_free:
- return True
- return False
-
- def _create_image_files(self, desired_size_bytes):
- """Create an image and return its assigned UUID.
-
- @param desired_size_bytes: Desired image size in bytes
- @type desired_size_bytes: int
- @rtype: string
- @return: uuid
-
- @raises XendError: If an error occurs.
- """
- self.lock.acquire()
- try:
- if not self._has_space_available_for(desired_size_bytes):
- raise XendError("Not enough space")
-
- image_uuid = uuid.createString()
- qcow_path = os.path.join(self.location,
- XEND_STORAGE_QCOW_FILENAME % image_uuid)
-
- if qcow_path and os.path.exists(qcow_path):
- raise XendError("Image with same UUID alreaady exists:" %
- image_uuid)
-
- cmd = QCOW_CREATE_COMMAND % (desired_size_bytes/MB, qcow_path)
- rc, output = commands.getstatusoutput(cmd)
-
- if rc != 0:
- # cleanup the image file
- os.unlink(qcow_path)
- raise XendError("Failed to create QCOW Image: %s" % output)
-
- self._refresh()
- return image_uuid
- finally:
- self.lock.release()
-
- def destroy_image(self, image_uuid):
- """Destroy an image that is managed by this storage repository.
-
- @param image_uuid: Image UUID
- @type image_uuid: String
- @rtype: String
- """
- self.lock.acquire()
- try:
- if image_uuid in self.images:
- # TODO: check if it is being used?
- qcow_path = self.images[image_uuid].qcow_path
- cfg_path = self.images[image_uuid].cfg_path
- try:
- os.unlink(qcow_path)
- if cfg_path and os.path.exists(cfg_path):
- os.unlink(cfg_path)
- except OSError:
- log.exception("Failed to destroy image")
- del self.images[image_uuid]
- return True
- finally:
- self.lock.release()
-
- return False
- def list_images(self):
- """ List all the available images by UUID.
-
- @rtype: list of strings.
- @return: list of UUIDs
- """
- self.lock.acquire()
- try:
- return self.images.keys()
- finally:
- self.lock.release()
- def free_space_bytes(self):
- """Returns the amount of available space in KB.
- @rtype: int
- """
- self.lock.acquire()
- try:
- return self.storage_free
- finally:
- self.lock.release()
-
- def total_space_bytes(self):
- """Returns the total usable space of the storage repo in KB.
- @rtype: int
- """
- self.lock.acquire()
- try:
- if self.storage_max == XEND_STORAGE_NO_MAXIMUM:
- stfs = os.statvfs(self.location)
- return stfs.f_blocks * stfs.f_frsize
- else:
- return self.storage_max
- finally:
- self.lock.release()
-
- def used_space_bytes(self):
- """Returns the total amount of space used by this storage repository.
- @rtype: int
- """
- self.lock.acquire()
- try:
- return self.storage_used
- finally:
- self.lock.release()
+ def is_valid_vdi(self, vdi_uuid):
+ return (vdi_uuid in self.images)
- def virtual_allocation(self):
- """Returns the total virtual space allocated within the storage repo.
- @rtype: int
- """
+ def get_vdi_by_uuid(self, image_uuid):
self.lock.acquire()
try:
- return self.storage_alloc
+ return self.images.get(image_uuid)
finally:
self.lock.release()
- def is_valid_vdi(self, vdi_uuid):
- return (vdi_uuid in self.images)
-
- def create_image(self, vdi_struct):
- image_uuid = None
- try:
- sector_count = int(vdi_struct.get('virtual_size', 0))
- sector_size = int(vdi_struct.get('sector_size', 1024))
- size_bytes = (sector_count * sector_size)
-
- image_uuid = self._create_image_files(size_bytes)
- image = self.images[image_uuid]
- image_cfg = {
- 'sector_size': sector_size,
- 'virtual_size': sector_count,
- 'type': vdi_struct.get('type', 'system'),
- 'name_label': vdi_struct.get('name_label', ''),
- 'name_description': vdi_struct.get('name_description', ''),
- 'sharable': bool(vdi_struct.get('sharable', False)),
- 'read_only': bool(vdi_struct.get('read_only', False)),
- }
-
- # load in configuration from vdi_struct
- image.load_config_dict(image_cfg)
-
- # save configuration to file
- cfg_filename = XEND_STORAGE_VDICFG_FILENAME % image_uuid
- cfg_path = os.path.join(self.location, cfg_filename)
- image.save_config(cfg_path)
-
- except Exception, e:
- # cleanup before raising exception
- if image_uuid:
- self.destroy_image(image_uuid)
-
- raise
-
- return image_uuid
-
- def xen_api_get_by_name_label(self, label):
+ def get_vdi_by_name_label(self, label):
self.lock.acquire()
try:
- for image_uuid, val in self.images.items():
- if val.name_label == label:
+ for image_uuid, image in self.images.items():
+ if image.name_label == label:
return image_uuid
return None
finally:
self.lock.release()
- def xen_api_get_by_uuid(self, image_uuid):
- self.lock.acquire()
- try:
- return self.images.get(image_uuid)
- finally:
- self.lock.release()
-
+ def get_vdis(self):
+ return self.images.keys()
-# remove everything below this line!!
-if __name__ == "__main__":
- xsr = XendStorageRepository()
- print 'Free Space: %d MB' % (xsr.free_space_bytes()/MB)
- print "Create Image:",
- print xsr._create_image_files(10 * MB)
- print 'Delete all images:'
- for image_uuid in xsr.list_images():
- print image_uuid,
- xsr._destroy_image_files(image_uuid)
+ def create_vdi(self, vdi_struct):
+ raise NotImplementedError()
- print
+ def destroy_vdi(self, vdi_struct):
+ raise NotImplementedError()
return True
- def get_record(self):
+ def get_record(self, transient = True):
return {'uuid': self.uuid,
'name_label': self.name_label,
'name_description': self.name_description,
'children': [],
'sharable': False,
'readonly': False,
- 'SR': self.sr.get_uuid(),
+ 'SR': self.sr_uuid,
'VBDs': []}
-
-class XendQCOWVDI(XendVDI):
+ def get_image_uri(self):
+ raise NotImplementedError()
+
+class XendQCoWVDI(XendVDI):
def __init__(self, uuid, sr_uuid, qcow_path, cfg_path, vsize, psize):
XendVDI.__init__(self, uuid, sr_uuid)
self.auto_save = False
self.sector_size = 512
self.auto_save = True
+ def get_image_uri(self):
+ return 'tap:qcow:%s' % self.qcow_path
+
+class XendLocalVDI(XendVDI):
+ def __init__(self, vdi_struct):
+ vdi_uuid = vdi_struct['uuid']
+ sr_uuid = vdi_struct['SR']
+ XendVDI.__init__(self, vdi_uuid, sr_uuid)
+
+ self.auto_save = False
+ self.cfg_path = None
+ self.name_label = vdi_struct.get('name_label','')
+ self.name_description = vdi_struct.get('name_description', '')
+ self.physical_utilisation = 0
+ self.virtual_size = 0
+ self.sector_size = 0
+ self.type = vdi_struct.get('type', '')
+ self.sharable = vdi_struct.get('sharable', False)
+ self.read_only = vdi_struct.get('read_only', False)
+ self.image_uri = vdi_struct.get('uri', 'file:/dev/null')
+
+ def get_image_uri(self):
+ return self.image_uri